/*
    Este programa é um software livre; você pode redistribuí-lo e/ou

    modificá-lo dentro dos termos da Licença Pública Geral GNU como

    publicada pela Fundação do Software Livre (FSF); na versão 2 da

    Licença, ou (na sua opinião) qualquer versão.



    Este programa é distribuído na esperança de que possa ser útil,

    mas SEM NENHUMA GARANTIA; sem uma garantia implícita de ADEQUAÇÃO a qualquer

    MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a

    Licença Pública Geral GNU para maiores detalhes.



    Você deve ter recebido uma cópia da Licença Pública Geral GNU

    junto com este programa, se não, escreva para a Fundação do Software

    Livre(FSF) Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
@todo testar múltiplas atribuições
*/


#include "matrix.h"
#include <iostream>
#include <iomanip>
#include <fstream>
#include <math.h>
#include <stdlib.h>

using namespace std;

void Matrix::put(unsigned int i, unsigned int j, double value)
{
    if ((i>=1)&&(i<=_Rows)&&(j>=1)&&(j<=_Cols))
        _Mat[(i-1)*_Cols +j-1]=value;
    else
    {
        cerr << "Elemento (" << i << " , " << j << ") está fora do suporte da matrix." << endl;
        exit(0);
    }
}


double Matrix::get(const unsigned int i, const unsigned int j) const
{
    if ((i>=1)&&(i<=_Rows)&&(j>=1)&&(j<=_Cols))
        return _Mat[(i-1)*_Cols +j-1];
    else
    {
        cerr << "Elemento (" << i << ", " << j << ") está fora do suporte da matrix." << endl;
        exit(0);
    }
}


Matrix::Matrix() :RectSupport(0, 0)
{
    //DEBUG(Construtor);
    _Mat = NULL;
    //construtor();//    construtor();
}


Matrix::Matrix(unsigned int parRows, unsigned int parCols):RectSupport(parRows, parCols)
{
    //DEBUG(Construtor);
    _Mat = NULL;
    nw(parRows, parCols);
    //construtor();
}


Matrix::Matrix(const Matrix& parMatrix):RectSupport(parMatrix._Rows,parMatrix._Cols)
{
    _Mat= new double [parMatrix.getRows()* parMatrix.getCols()];
    for(unsigned int i = 0; i < parMatrix.getRows()* parMatrix.getCols(); i++)
        *(_Mat+i) = *(parMatrix._Mat+i);
    //construtor();
}

Matrix::Matrix(Matrix&& matrix):RectSupport(matrix._Rows,matrix._Cols)
{
    Matrix parMatrix = matrix;
    _Mat= new double [parMatrix.getRows()* parMatrix.getCols()];
    for(unsigned int i = 0; i < parMatrix.getRows()* parMatrix.getCols(); i++)
        *(_Mat+i) = *(parMatrix._Mat+i);
    //construtor();
}

Matrix::~Matrix()
{
    //DEBUG(Destrutor);
    //destrutor();
    del();
    //delete [] _Mat;
}


void Matrix::del()
{
    if (_Mat)
        delete [] _Mat;
	_Mat=NULL;
	_Rows =0;
	_Cols=0;
}


void Matrix::nw(const unsigned int parRows, const unsigned int parCols)
{
    del();
    if(parRows && parCols)
    {
        _Rows = parRows;
        _Cols = parCols;
        _Mat= new double[parRows * parCols];
        for(unsigned int i=0; i < parRows * parCols; i++)
            _Mat[i]=0;
    }
}


bool Matrix::istriang() const
{
   for (unsigned int j=1; j<=_Cols; j++)
   {
      if ((abs((long)(1-get(j, j))))>0.000001)
         return 0;
      for (unsigned int i=(j+1);i<=_Rows;i++)
         if (abs((long)get(i, j))>0.000001)
            return 0;
   }
   return 1;
}


Matrix Matrix::operator |(Matrix& parMatrix)
{
    if (parMatrix._Rows!=_Rows)
    {
        cerr << "Error detected by the & operator:" << endl<< "Both matrixes must have the same number of rows." << endl;
        exit (1);
    }
    else
    {
        Matrix result(_Rows,_Cols+parMatrix._Cols);
        for(unsigned int i=1; i<=_Rows;i++)
            for(unsigned int j=1; j <= _Cols+parMatrix._Cols; j++)
                if(j <= _Cols)
                    result.put(i,j,get(i,j));
                else
                    result.put(i,j,parMatrix.get(i,j - _Cols));
        return result;
    }
}

Matrix Matrix::operator |(Matrix&& parMatrix)
{
    if (parMatrix._Rows!=_Rows)
    {
        cerr << "Error detected by the & operator:" << endl<< "Both matrixes must have the same number of rows." << endl;
        exit (1);
    }
    else
    {
        Matrix result(_Rows,_Cols+parMatrix._Cols);
        for(unsigned int i=1; i<=_Rows;i++)
            for(unsigned int j=1; j <= _Cols+parMatrix._Cols; j++)
                if(j <= _Cols)
                    result.put(i,j,get(i,j));
                else
                    result.put(i,j,parMatrix.get(i,j - _Cols));
        return result;
    }
}


Matrix Matrix::operator +(Matrix& parMatrix)
{
    if ((parMatrix._Rows!=_Rows)||(parMatrix._Cols!=_Cols) )
    {
      cerr << "Error detected by the + operator: Both matrixes must have the same dimensions." << endl;
      exit (0);
    }
    else
    {
        Matrix result(_Rows, _Cols);
        for (int i=0; i<(_Rows*_Cols); i++)
        {
            result._Mat[i]=_Mat[i] + parMatrix._Mat[i];
        }
        return result;
    }
}

Matrix Matrix::operator +(Matrix&& matrix)
{
    Matrix parMatrix = matrix;
    if ((parMatrix._Rows!=_Rows)||(parMatrix._Cols!=_Cols) )
    {
      cerr << "Error detected by the + operator: Both matrixes must have the same dimensions." << endl;
      exit (0);
    }
    else
    {
        Matrix result(_Rows, _Cols);
        for (int i=0; i<(_Rows*_Cols); i++)
        {
            result._Mat[i]=_Mat[i] + parMatrix._Mat[i];
        }
        return result;
    }
}


Matrix Matrix::operator -(Matrix& parMatrix)
{
    if ((parMatrix._Rows!=_Rows)||(parMatrix._Cols!=_Cols) )
    {
      cerr << "Error detected by the + operator: Both matrixes must have the same dimensions." << endl;
      exit (0);
    }
    else
    {
        Matrix result(_Rows, _Cols);
        for (int i=0; i<(_Rows*_Cols); i++)
        {
            result._Mat[i]=_Mat[i] - parMatrix._Mat[i];
        }
        return result;
    }
}

Matrix Matrix::operator -(Matrix&& matrix)
{
    Matrix parMatrix = matrix;
    if ((parMatrix._Rows!=_Rows)||(parMatrix._Cols!=_Cols) )
    {
      cerr << "Error detected by the + operator: Both matrixes must have the same dimensions." << endl;
      exit (0);
    }
    else
    {
        Matrix result(_Rows, _Cols);
        for (int i=0; i<(_Rows*_Cols); i++)
        {
            result._Mat[i]=_Mat[i] - parMatrix._Mat[i];
        }
        return result;
    }
}

Matrix Matrix::operator *(Matrix& parMatrix)
{
    if ((parMatrix._Rows!=_Cols))
    {
      cerr << "Error detected by the * operator: First matrix columns and second matrix lines must be the same." << endl;
      exit (0);
    }
    else
    {
        Matrix result(_Rows, parMatrix._Cols);
        for (unsigned int i=1; i <= _Rows; i++)
        {
            for (unsigned int j=1; j <= parMatrix._Cols; j++)
            {
                double element = 0;
                for (unsigned int k=1; k <= _Cols; k++)
                {
                    element += this->get(i,k) * parMatrix.get(k,j);
                }
                result.put(i,j,element);
            }
        }
        return result;
    }
}

Matrix Matrix::operator *(Matrix&& matrix)
{
    Matrix parMatrix = matrix;
    if ((parMatrix._Rows!=_Cols))
    {
      cerr << "Error detected by the * operator: First matrix columns and second matrix lines must be the same." << endl;
      exit (0);
    }
    else
    {
        Matrix result(_Rows, parMatrix._Cols);
        for (unsigned int i=1; i <= _Rows; i++)
        {
            for (unsigned int j=1; j <= parMatrix._Cols; j++)
            {
                double element = 0;
                for (unsigned int k=1; k <= _Cols; k++)
                {
                    element += this->get(i,k) * parMatrix.get(k,j);
                }
                result.put(i,j,element);
            }
        }
        return result;
    }
}

Matrix Matrix::operator +(double parScalar)
{
   Matrix result = *this;
   for(unsigned int i=0; i<_Rows*_Cols; i++)
       (*(result._Mat+i))+=parScalar;
   return result;
}


Matrix Matrix::operator -(double parScalar)
{
   Matrix result = *this;
   for(unsigned int i=0; i<_Rows*_Cols; i++)
       (*(result._Mat+i))-=parScalar;
   return result;
}


Matrix Matrix::operator *(double parScalar)
{
   Matrix result = *this;
   for(unsigned int i=0; i<_Rows*_Cols; i++)
       (*(result._Mat+i))*=parScalar;
   return result;
}


Matrix Matrix::operator /(double parScalar)
{
   Matrix result = *this;
   for(unsigned int i=0; i<_Rows*_Cols; i++)
       (*(result._Mat+i))/=parScalar;
   return result;
}


Matrix &Matrix::operator =(const Matrix& Par_Matrix)
{
    if ((_Rows!=Par_Matrix._Rows)||(_Cols!=Par_Matrix._Cols))
    {
        del();
        nw(Par_Matrix._Rows,Par_Matrix._Cols);
    }
    for (unsigned int i=0; i < _Rows*_Cols ; i++)
    {
        _Mat[i]=Par_Matrix._Mat[i];
    }
    return *this;
}

Matrix &Matrix::operator =(const Matrix&& matrix)
{
    Matrix Par_Matrix = matrix;
    if ((_Rows!=Par_Matrix._Rows)||(_Cols!=Par_Matrix._Cols))
    {
        del();
        nw(Par_Matrix._Rows,Par_Matrix._Cols);
    }
    for (unsigned int i=0; i < _Rows*_Cols ; i++)
    {
        _Mat[i]=Par_Matrix._Mat[i];
    }
    return *this;
}


Matrix Matrix::cut(const unsigned int FirstRow, const unsigned int LastRow, const unsigned int FirstCol, const unsigned int LastCol)
{
    if((FirstRow>LastRow)||(FirstCol>LastCol)||(FirstRow<1)||(FirstCol<1)||(LastRow>_Rows)||(LastCol>_Cols))
    {
        cerr << "Erro detectado pelo método Matrix::cut() method:" << endl << "Parâmetros de entrada fora da faixa."<<endl;
        exit (1);
    }
    Matrix result(LastRow - FirstRow +1, LastCol - FirstCol+1);
    for(unsigned int i=1; i<=result._Rows; i++)
        for(unsigned int j=1; j<=result._Cols; j++)
            result.put(i,j,get(i+FirstRow-1, j+FirstCol-1));
    return result;
}


void Matrix::show()
{
  cout << "Matrix " << _Rows << 'x' << _Cols << endl;
  cout <<  setprecision(2) ;
  for (unsigned int i=1;i<=_Rows;i++)
  {
       for (unsigned int j=1;j<=_Cols;j++)
           cout<< setw(5)<< get ((unsigned int) i, (unsigned int) j) << ' ';
       cout << endl;
  }
  cout << endl;
}

Matrix triang(Matrix& parMatrix)
{
   Matrix Result = parMatrix;
   Matrix LastResult;
   for (unsigned int i=1; i<=parMatrix.getRows(); i++) // for each row
   {
      LastResult=Result;
      for(unsigned int j=i; j<=parMatrix.getCols(); j++)
      {
         Result.put(i,j,(Result.get(i,j)/LastResult.get(i,i))); // divides all elements of row i per the element i, i of LastInteraction
         for(unsigned int i2=i+1; i2 <= parMatrix.getRows(); i2++)
            Result.put(i2,j, Result.get(i2,j)-Result.get(i,j)*LastResult.get(i2,i) ); // makes the elements in the column j of the next rows of the matrix equal to zero using linear combination of line
      }
   }
   return Result;
}

Matrix triang(Matrix&& parMatrix)
{
   Matrix Result = parMatrix;
   Matrix LastResult;
   for (unsigned int i=1; i<=parMatrix.getRows(); i++) // for each row
   {
      LastResult=Result;
      for(unsigned int j=i; j<=parMatrix.getCols(); j++)
      {
         Result.put(i,j,(Result.get(i,j)/LastResult.get(i,i))); // divides all elements of row i per the element i, i of LastInteraction
         for(unsigned int i2=i+1; i2 <= parMatrix.getRows(); i2++)
            Result.put(i2,j, Result.get(i2,j)-Result.get(i,j)*LastResult.get(i2,i) ); // makes the elements in the column j of the next rows of the matrix equal to zero using linear combination of line
      }
   }
   return Result;
}

Matrix identity(unsigned int size)
{
    Matrix result(size, size);
    for (unsigned int i=1; i<=size; i++)
        result.put(i,i,1.0);
    return result;
}

Matrix ones(unsigned int size)
{
    Matrix result(size,size);
    for (unsigned int i=1; i<= size; i++)
        for (unsigned int j=1; j<= size; j++)
            result.put(i,j,1.0);
    return result;
}

Matrix ones(unsigned int parRows, unsigned int parCols)
{
    Matrix result(parRows,parCols);
    for (unsigned int i=1; i<= parRows; i++)
        for (unsigned int j=1; j<= parCols; j++)
            result.put(i,j,1.0);
    return result;
}


Matrix zeros(unsigned int size)
{
    Matrix result(size,size);
    return result;
}


Matrix zeros(unsigned int parRows, unsigned int parCols)
{
    Matrix result(parRows,parCols);
    return result;
}


Matrix transpose(Matrix& parMatrix)
{
    Matrix result (parMatrix.getCols() , parMatrix.getRows());
    for (unsigned int i=1; i<=parMatrix.getCols(); i++)
        for(unsigned int j=1; j<=parMatrix.getRows(); j++)
            result.put(i,j,parMatrix.get(j,i));
    return result;
}

Matrix transpose(Matrix&& parMatrix)
{
    Matrix result (parMatrix.getCols() , parMatrix.getRows());
    for (unsigned int i=1; i<=parMatrix.getCols(); i++)
        for(unsigned int j=1; j<=parMatrix.getRows(); j++)
            result.put(i,j,parMatrix.get(j,i));
    return result;
}


Matrix reduction(Matrix& parMatrix)
{

   Matrix Result;
   Matrix LastResult;
   Result = parMatrix;
   if (sqrt(pow(Result.get(1,1),2))<0.0000000001)
    {
        double MaximumValueinFirstColumn=0; // maximum absolute value in first columns
        unsigned int Maximum_Row=0;
        for(unsigned int i=1; i<=parMatrix.getRows(); i++)
        {
            if (sqrt(pow(Result.get(i,1),2))>MaximumValueinFirstColumn)
            {
                Maximum_Row=i;
                MaximumValueinFirstColumn=Result.get(i,1);
            }
        }
        LastResult = Result;
        for (unsigned int j=1; j<=parMatrix.getCols(); j++) //permutes the rows
        {
            Result.put(1,j,LastResult.get(Maximum_Row,j));
            Result.put(Maximum_Row, j, LastResult.get(1,j));
        }
    }
// end of pivoting
   for (unsigned int i=parMatrix.getRows(); i>=1; i--) // for each row
   {
      LastResult=Result;
      for(unsigned int j=parMatrix.getCols(); j >= 1; j--)
      {
         Result.put(i,j,(Result.get(i,j)/LastResult.get(i,i))); // divides all elements of row i per the element i, i of LastInteraction
         for(unsigned int i2=i-1; i2>=1; i2--)
            Result.put(i2,j,( Result.get(i2,j) - Result.get(i,j)*LastResult.get(i2,i)) ); // makes the elements in the column j of the next rows of the matrix equal to zero using linear combination of line
      }
   }
   return Result;
}

Matrix reduction(Matrix&& parMatrix)
{

   Matrix Result;
   Matrix LastResult;
   Result = parMatrix;
   if (sqrt(pow(Result.get(1,1),2))<0.0000000001)
    {
        double MaximumValueinFirstColumn=0; // maximum absolute value in first columns
        unsigned int Maximum_Row=0;
        for(unsigned int i=1; i<=parMatrix.getRows(); i++)
        {
            if (sqrt(pow(Result.get(i,1),2))>MaximumValueinFirstColumn)
            {
                Maximum_Row=i;
                MaximumValueinFirstColumn=Result.get(i,1);
            }
        }
        LastResult = Result;
        for (unsigned int j=1; j<=parMatrix.getCols(); j++) //permutes the rows
        {
            Result.put(1,j,LastResult.get(Maximum_Row,j));
            Result.put(Maximum_Row, j, LastResult.get(1,j));
        }
    }
// end of pivoting
   for (unsigned int i=parMatrix.getRows(); i>=1; i--) // for each row
   {
      LastResult=Result;
      for(unsigned int j=parMatrix.getCols(); j >= 1; j--)
      {
         Result.put(i,j,(Result.get(i,j)/LastResult.get(i,i))); // divides all elements of row i per the element i, i of LastInteraction
         for(unsigned int i2=i-1; i2>=1; i2--)
            Result.put(i2,j,( Result.get(i2,j) - Result.get(i,j)*LastResult.get(i2,i)) ); // makes the elements in the column j of the next rows of the matrix equal to zero using linear combination of line
      }
   }
   return Result;
}

Matrix inverse(Matrix& parMatrix)
{
    if (parMatrix.getCols()!=parMatrix.getRows())
    {
        cerr << "Error detected by the inversion algorithm." << endl << "Matrix must be square."<< endl;
        exit (1);
    }
    else
    {
        Matrix I = identity(parMatrix.getRows());
        Matrix A_I = parMatrix | I;
        A_I = triang (A_I);
        A_I= reduction(A_I);
        Matrix Inv = A_I.cut(1, parMatrix.getRows(), parMatrix.getCols()+1, 2*parMatrix.getCols());
        return Inv;
    }
}

Matrix inverse(Matrix&& parMatrix)
{
    if (parMatrix.getCols()!=parMatrix.getRows())
    {
        cerr << "Error detected by the inversion algorithm." << endl << "Matrix must be square."<< endl;
        exit (1);
    }
    else
    {
        Matrix I = identity(parMatrix.getRows());
        Matrix A_I = parMatrix | I;
        A_I = triang (A_I);
        A_I= reduction(A_I);
        Matrix Inv = A_I.cut(1, parMatrix.getRows(), parMatrix.getCols()+1, 2*parMatrix.getCols());
        return Inv;
    }
}


Matrix maxHoriz(Matrix& parMatrix)
{
    Matrix Maximum;
    Maximum = parMatrix.cut(1,parMatrix.getRows(),1,1);
    for (unsigned int j=2; j<=parMatrix.getCols(); j++)
        for(unsigned int i=1; i<=parMatrix.getRows(); i++)
            if (parMatrix.get(i,j)>Maximum.get(i,1))
                Maximum.put(i,1,parMatrix.get(i,j));
    return Maximum;
}

Matrix maxHoriz(Matrix&& parMatrix)
{
    Matrix Maximum;
    Maximum = parMatrix.cut(1,parMatrix.getRows(),1,1);
    for (unsigned int j=2; j<=parMatrix.getCols(); j++)
        for(unsigned int i=1; i<=parMatrix.getRows(); i++)
            if (parMatrix.get(i,j)>Maximum.get(i,1))
                Maximum.put(i,1,parMatrix.get(i,j));
    return Maximum;
}

Matrix maxVert(Matrix& parMatrix)
{
    Matrix Trans, Result;
    Trans = transpose(parMatrix);
    Result = maxHoriz(Trans);
    Result = transpose(Result);
    return Result;
}

Matrix maxVert(Matrix&& parMatrix)
{
    Matrix Trans, Result;
    Trans = transpose(parMatrix);
    Result = maxHoriz(Trans);
    Result = transpose(Result);
    return Result;
}

Matrix max(Matrix& parMatrix)
{
    Matrix Result;
    Result = maxHoriz(parMatrix);
    Result = maxVert(Result);
    return Result;
}

Matrix max(Matrix&& parMatrix)
{
    Matrix Result;
    Result = maxHoriz(parMatrix);
    Result = maxVert(Result);
    return Result;
}

Matrix minHoriz(Matrix& parMatrix)
{
    Matrix Minimum;
    Minimum = parMatrix.cut(1,parMatrix.getRows(),1,1);
    for (unsigned int j=2; j<=parMatrix.getCols(); j++)
        for(unsigned int i=1; i<=parMatrix.getRows(); i++)
            if (parMatrix.get(i,j)<Minimum.get(i,1))
                Minimum.put(i,1,parMatrix.get(i,j));
    return Minimum;
}

Matrix minHoriz(Matrix&& parMatrix)
{
    Matrix Minimum;
    Minimum = parMatrix.cut(1,parMatrix.getRows(),1,1);
    for (unsigned int j=2; j<=parMatrix.getCols(); j++)
        for(unsigned int i=1; i<=parMatrix.getRows(); i++)
            if (parMatrix.get(i,j)<Minimum.get(i,1))
                Minimum.put(i,1,parMatrix.get(i,j));
    return Minimum;
}

Matrix minVert(Matrix& parMatrix)
{
    Matrix Trans,Result;
    Trans = transpose(parMatrix);
    Result = minHoriz(Trans);
    Result = transpose(Result);
    return Result;
}

Matrix minVert(Matrix&& parMatrix)
{
    Matrix Trans,Result;
    Trans = transpose(parMatrix);
    Result = minHoriz(Trans);
    Result = transpose(Result);
    return Result;
}

Matrix min(Matrix& parMatrix)
{
    Matrix Result;
    Result = minHoriz(parMatrix);
    Result = minVert(Result);
    return Result;
}

Matrix min(Matrix&& parMatrix)
{
    Matrix Result;
    Result = minHoriz(parMatrix);
    Result = minVert(Result);
    return Result;
}
